package searchPack;

import directoryPath.CustomizedDirectory;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import org.apache.lucene.index.CorruptIndexException;

import org.apache.lucene.queryParser.ParseException;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.index.TermEnum;
import org.apache.lucene.index.TermFreqVector;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.similar.MoreLikeThis;
import org.apache.lucene.util.Version;
import weka.clusterers.RandomizableClusterer;

import weka.clusterers.SimpleKMeans;
import weka.clusterers.XMeans;
import weka.core.Attribute;
import weka.core.FastVector;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.Stopwords;


public class SearchManager {

    private Analyzer analyzer;
    private CustomizedDirectory indexDir;
    private IndexSearcher indexSearcher = null;
    private TopDocs hits = null;
    private ArrayList<Integer> resultsDocsNums = new ArrayList<Integer>();
    

    public SearchManager(Analyzer analyzer) {
        this.analyzer = analyzer;
    }

    public String createQuery(String allWords, String exactContent, String anyWords, String excludeWords) throws CorruptIndexException, IOException, ParseException {

        String query = new FindAllWords().buildQuery(allWords) + " ";
        query += new FindAnyWord().buildQuery(anyWords) + " ";
        query += new FindExcludeWords().buildQuery(excludeWords) + " ";
        query += new FindExactContent().buildQuery(exactContent) + " ";

        return query.trim();
    }

    public ArrayList<String> search(String field, String query, int maxResults)
            throws IOException, ParseException {

        ArrayList<String> results = new ArrayList<String>();
        QueryParser parser = new QueryParser(Version.LUCENE_33, field, analyzer);
        if(!"".equals(query)) {
            Query q = parser.parse(query);
            // Realizamos la búsqueda en cantidad de resultados
            this.hits = indexSearcher.search(q, maxResults);
                 
            if(hits.totalHits > 0){
                if (maxResults > hits.totalHits) {
                    maxResults = hits.totalHits;
                }
                ScoreDoc scoreDoc = null;
                for (int i=0; i<maxResults; i++) {
                    scoreDoc = hits.scoreDocs[i];
                    results.add(indexSearcher.doc(scoreDoc.doc).getFieldable("direccion").stringValue());
                    results.add(String.valueOf(scoreDoc.score));
                    resultsDocsNums.add(scoreDoc.doc);
                }
            }
        }
        return results;
    }

    public int getTotalNumResults() {
        if (this.hits != null) {
            return this.hits.totalHits;
        }
        return 0;
    }

    public void createIndexSearcher(CustomizedDirectory indexDir) throws CorruptIndexException, IOException {
        this.indexDir = indexDir;
        this.indexSearcher = new IndexSearcher(indexDir.getDirectory(), false);     
    }

    public IndexSearcher getIndex() {
        return this.indexSearcher;
    }

    public void closeIndex() throws IOException {
        if (indexSearcher != null) {
            indexSearcher.close();
        }
    }

    public ArrayList getSimilarFiles(String archivoPath, int max) throws IOException {

        IndexReader indexReader = indexSearcher.getIndexReader();
        MoreLikeThis mlt = new MoreLikeThis(indexReader);
        String[] field = {"contenido"};
        mlt.setMaxQueryTerms(2);
        mlt.setMaxDocFreq(5);
        mlt.setAnalyzer(analyzer);
        mlt.setFieldNames(field);
        File f = new File(archivoPath);
        Query query1 = mlt.like(f); // mlt.like(docnumber)
        
        //  TopDocs matches = null;
        this.hits = indexSearcher.search(query1,max);
        
        if(hits.totalHits > 0) {
            //  if (maxResults > hits.totalHits) maxResults = hits.totalHits;
            ArrayList<String> results = new ArrayList<String>();
            ScoreDoc scoreDoc = null;
            for (int i=0; i<hits.totalHits; i++) {
                scoreDoc = hits.scoreDocs[i];
                results.add(indexSearcher.doc(scoreDoc.doc).getFieldable("direccion").stringValue());
                results.add(String.valueOf(scoreDoc.score));
            }
            return results;
        }
        return null;
    }

    public ArrayList<String> clusterResults(String field, int numHits) throws IOException, Exception {
        ArrayList<String> resultClusters = new ArrayList<String>();
        Instances dataSet = getInstances(field, numHits);
        if (field.equals("contenido")) {
            XMeans clusterer = new XMeans();
            clusterer.setMinNumClusters(numHits);
            clusterer.setMaxNumClusters(numHits);
            resultClusters = getClusters(dataSet, clusterer);
            resultClusters = minimizeClusters(resultClusters);
        } else {
            SimpleKMeans clusterer = new SimpleKMeans();
            clusterer.setNumClusters(numHits);
            resultClusters = getClusters(dataSet, clusterer);
        }
        return resultClusters;
    }

    private ArrayList<String> getClusters(Instances dataSet, RandomizableClusterer clusterer) throws Exception {
        // Borro el atributo del id de los archivos
        dataSet.deleteAttributeAt(0);
        clusterer.buildClusterer(dataSet); // Se crean los clusters
        int numClusters = clusterer.numberOfClusters(); // Se obtiene la cantidad de clusters
        // Lleva en cada entrada la lista de documentos de cada cluster
        HashMap<Integer,ArrayList<String>> clusteredResults = new HashMap<Integer,ArrayList<String>>();
        // Inicializo la hash con los numeros de clsuters y las listas vacias de docs que tiene cada uno
        ArrayList<String> docsList;
        for (int i=0; i<numClusters; i++) {
            // Agrego al principio de la lista de cada cluster el nombre del mismo y un lugar vacio indicando
            // que no posee score
            docsList = new ArrayList<String>();
            docsList.add("Grupo " + String.valueOf(i));
            docsList.add("");
            clusteredResults.put(i, docsList);
        }

        ScoreDoc scoreDoc = null;
        ArrayList<String> actualCluster;
        for (int i=0; i<dataSet.numInstances(); i++){
            // Asigno la instancia analizada a un cluster y obtengo como resultado el numero del mismo
            int assignedCluster = clusterer.clusterInstance(dataSet.instance(i));
            // Obtengo la lista de archivos del cluster que se asigno
            actualCluster = clusteredResults.get(assignedCluster);
            scoreDoc = hits.scoreDocs[i];
            // Agrego el path completo del documento y su score en el cluster que corresponde
            actualCluster.add(indexSearcher.doc(scoreDoc.doc).getFieldable("direccion").stringValue());
            actualCluster.add(String.valueOf(scoreDoc.score));
        }

        // Uno todas las listas de docuemntos en una sola para devolverla como resultado
        ArrayList<String> finalResults = new ArrayList<String>();
        for (int i=0; i<numClusters; i++) {
            finalResults.addAll(clusteredResults.get(i));
        }

        return finalResults;
    }

    private Instances getInstances(String field, int maxHits) throws IOException {
    
        IndexReader indexReader = indexSearcher.getIndexReader();
        TermEnum allTerms = indexReader.terms();    // Obtenemos todos los terminos del indice cargado
        FastVector importantTerms = new FastVector();
        // Se Agrega el atributo "file_att" que indica los ids de los documentos del indice
        Attribute fileAtt = new Attribute("file_att");
        importantTerms.addElement(fileAtt);
    
        // TODO: usar un Set en vez de una lista, mas efectivo para buscar despues 
        HashSet<String> includedTerms = new HashSet<String>();
        for (int i=0; i<maxHits; i++) {
            ScoreDoc scoreDoc = hits.scoreDocs[i];
            TermFreqVector vec = indexReader.getTermFreqVector(scoreDoc.doc, field);
            includedTerms.addAll(Arrays.asList(vec.getTerms()));
        }
    
        ArrayList<String> analizedTerms = new ArrayList<String>();
        Term actualTerm;
        while (allTerms.next()) {
            actualTerm = allTerms.term();
            String termName = actualTerm.text();
            if((!analizedTerms.contains(termName)) &&
               (includedTerms.contains(termName)) && // Si el termino esta en alguno de los archivos de resultado
               (!Stopwords.isStopword(termName))) {
               //(termName.matches("[a-z|A-Z].*"))) {

               TermDocs docs = indexReader.termDocs(actualTerm); // obtengo los documentos que tienen el termino
               int acumFreq = 0; // Frecuencias acumuladas del termino actual en los documentos
               int freq = 0; // Frecuencia del termino en el documento analizado en ese momento
               while(docs.next()) {
                   if (resultsDocsNums.contains(docs.doc())) {
                       freq = docs.freq();
                       acumFreq += freq;
                   }
                }
                if((field != "contenido") || (field == "contenido" && acumFreq > 50)){ // Si el termino aparece mas de 10 veces en total en todos los documentos
                    Attribute att = new Attribute(actualTerm.text());
                    importantTerms.addElement(att);
                }

                analizedTerms.add(termName);
            }
        }

        Instances dataSet = new Instances("Convertion_Lucene_Arff", importantTerms, maxHits);
        int termsCount = importantTerms.size();

        for (int i=0; i<maxHits; i++) {
            // Se crea la nueva instancia que guardara una fila de datos del archivo arff
            Instance inst = new Instance(termsCount);
            int docId = hits.scoreDocs[i].doc;
            // Se Agrega el id del documento como primer valor de la instancia
            inst.setValue((Attribute)importantTerms.elementAt(0), docId);
            // Obtengo el vector con las frecuencias de todos los terminos en el documento actual
            TermFreqVector freqVector = indexReader.getTermFreqVector(docId, field);
            // Obtengo el arreglo con todas las frecuencias de los terminos del documento
            int[] allTermFreq = freqVector.getTermFrequencies();
            for (int j=1; j<termsCount; j++) {
                Attribute att = (Attribute) importantTerms.elementAt(j);
                int termFreq = 0;
                // Obtengo el la posicion del termino dentro del documento
                int termIndex = freqVector.indexOf(att.name());
                if (termIndex != -1) { // Si el termino esta en el documento obtengo su frecuencia en el mismo
                    termFreq = allTermFreq[termIndex];
                }
                inst.setValue(att, termFreq);
            }
            dataSet.add(inst);  // Se agrega la nueva instancia al dataset
        }

        return dataSet;
    }

    public ArrayList<String> minimizeClusters(ArrayList<String> resultClusters) {
        ArrayList<String> minimizedClusters = new ArrayList<String>();
        int count = 0;
        int resultsSize = resultClusters.size();
        for (int i=0; i<resultsSize; i++) {
            String value = resultClusters.get(i);
            if ((!value.matches(".*Grupo [0-9].*")) || (value.matches(".*Grupo [0-9].*") && resultsSize > i+2
                    && !resultClusters.get(i+2).matches(".*Grupo [0-9].*"))) {
                if (value.matches(".*Grupo [0-9].*")) {
                    minimizedClusters.add("Grupo " + count);
                    count++;
                } else {
                    minimizedClusters.add(value);
                }
            } else {
                i++;
            }
        }
        if (minimizedClusters.get(minimizedClusters.size()-2).matches(".*Grupo [0-9].*")) {
            minimizedClusters.remove(minimizedClusters.size()-1);
            minimizedClusters.remove(minimizedClusters.size()-1);
        }
        return minimizedClusters;
    }
}
